#ifndef __LEARNDX_CCUSORCONTROL_H_INCLUDE__
#define __LEARNDX_CCUSORCONTROL_H_INCLUDE__
#include "mathtools\mathtools.h"
#include <Windows.h>
//#include <windowsx.h>
#include <vector>
#include "IReferenceCounted.h"

namespace ldx
{
	namespace device
	{
		class WinDevice;

		//! Default icons for cursors
		enum ECURSOR_ICON
		{
			// Following cursors might be system specific, or might use an Irrlicht icon-set. No guarantees so far.
			ECI_NORMAL,		// arrow
			ECI_CROSS,		// Crosshair
			ECI_HAND, 		// Hand
			ECI_HELP,		// Arrow and question mark
			ECI_IBEAM,		// typical text-selection cursor
			ECI_NO, 		// should not click icon
			ECI_WAIT, 		// hourclass
			ECI_SIZEALL,  	// arrow in all directions
			ECI_SIZENESW,	// resizes in direction north-east or south-west
			ECI_SIZENWSE, 	// resizes in direction north-west or south-east
			ECI_SIZENS, 	// resizes in direction north or south
			ECI_SIZEWE, 	// resizes in direction west or east
			ECI_UP,			// up-arrow

							// Implementer note: Should we add system specific cursors, which use guaranteed the system icons,
							// then I would recommend using a naming scheme like ECI_W32_CROSS, ECI_X11_CROSSHAIR and adding those
							// additionally.

							ECI_COUNT		// maximal of defined cursors. Note that higher values can be created at runtime
		};

		//! Names for ECURSOR_ICON
		const char* const GUICursorIconNames[ECI_COUNT + 1] =
		{
			"normal",
			"cross",
			"hand",
			"help",
			"ibeam",
			"no",
			"wait",
			"sizeall",
			"sizenesw",
			"sizenwse",
			"sizens",
			"sizewe",
			"sizeup",
			0
		};


		//! platform specific behavior flags for the cursor
		enum ECURSOR_PLATFORM_BEHAVIOR
		{
			//! default - no platform specific behavior
			ECPB_NONE = 0,

			//! On X11 try caching cursor updates as XQueryPointer calls can be expensive.
			/** Update cursor positions only when the irrlicht timer has been updated or the timer is stopped.
			This means you usually get one cursor update per device->run() which will be fine in most cases.
			See this forum-thread for a more detailed explanation:
			http://irrlicht.sourceforge.net/forum/viewtopic.php?f=7&t=45525
			*/
			ECPB_X11_CACHE_UPDATES = 1
		};


		//! Implementation of the win32 cursor control
		class CCursorControl:public IReferenceCounted
		{
		public:

			CCursorControl(WinDevice* device, const mt::dimension2du& wsize, HWND hwnd, bool fullscreen);
			~CCursorControl();

			//! Changes the visible state of the mouse cursor.
			virtual void setVisible(bool visible)
			{
				CURSORINFO info;
				info.cbSize = sizeof(CURSORINFO);
				BOOL gotCursorInfo = GetCursorInfo(&info);
				while (gotCursorInfo)
				{
#ifdef CURSOR_SUPPRESSED
					// new flag for Windows 8, where cursor
					// might be suppressed for touch interface
					if (info.flags == CURSOR_SUPPRESSED)
					{
						visible = false;
						break;
					}
#endif
					if ((visible && info.flags == CURSOR_SHOWING) || // visible
						(!visible && info.flags == 0)) // hidden
					{
						break;
					}
					// this only increases an internal
					// display counter in windows, so it
					// might have to be called some more
					const int showResult = ShowCursor(visible);
					// if result has correct sign we can
					// stop here as well
					if ((!visible && showResult < 0) ||
						(visible && showResult >= 0))
						break;
					// yes, it really must be set each time
					info.cbSize = sizeof(CURSORINFO);
					gotCursorInfo = GetCursorInfo(&info);
				}
				IsVisible = visible;
			}

			//! Returns if the cursor is currently visible.
			virtual bool isVisible() const
			{
				return IsVisible;
			}

			//! Sets the new position of the cursor.
			virtual void setPosition(const mt::position2df &pos)
			{
				setPosition(pos.X, pos.Y);
			}

			//! Sets the new position of the cursor.
			virtual void setPosition(float x, float y)
			{
				if (!UseReferenceRect)
					setPosition(mt::round32(x*WindowSize.Width), mt::round32(y*WindowSize.Height));
				else
					setPosition(mt::round32(x*ReferenceRect.getWidth()), mt::round32(y*ReferenceRect.getHeight()));
			}

			//! Sets the new position of the cursor.
			virtual void setPosition(const mt::position2d<int> &pos)
			{
				setPosition(pos.X, pos.Y);
			}

			//! Sets the new position of the cursor.
			virtual void setPosition(int x, int y)
			{
				if (UseReferenceRect)
				{
					SetCursorPos(ReferenceRect.UpperLeftCorner.X + x,
						ReferenceRect.UpperLeftCorner.Y + y);
				}
				else
				{
					RECT rect;
					if (GetWindowRect(HWnd, &rect))
						SetCursorPos(x + rect.left + BorderX, y + rect.top + BorderY);
				}

				CursorPos.X = x;
				CursorPos.Y = y;
			}

			//! Returns the current position of the mouse cursor.
			virtual const mt::position2d<int>& getPosition()
			{
				updateInternalCursorPosition();
				return CursorPos;
			}

			//! Returns the current position of the mouse cursor.
			virtual mt::position2df getRelativePosition()
			{
				updateInternalCursorPosition();

				if (!UseReferenceRect)
				{
					return mt::position2df(CursorPos.X * InvWindowSize.Width,
						CursorPos.Y * InvWindowSize.Height);
				}

				return mt::position2df(CursorPos.X / (float)ReferenceRect.getWidth(),
					CursorPos.Y / (float)ReferenceRect.getHeight());
			}

			//! Sets an absolute reference rect for calculating the cursor position.
			virtual void setReferenceRect(mt::rect<int>* rect = 0)
			{
				if (rect)
				{
					ReferenceRect = *rect;
					UseReferenceRect = true;

					// prevent division through zero and uneven sizes

					if (!ReferenceRect.getHeight() || ReferenceRect.getHeight() % 2)
						ReferenceRect.LowerRightCorner.Y += 1;

					if (!ReferenceRect.getWidth() || ReferenceRect.getWidth() % 2)
						ReferenceRect.LowerRightCorner.X += 1;
				}
				else
					UseReferenceRect = false;
			}

			/** Used to notify the cursor that the window was resized. */
			virtual void OnResize(const mt::dimension2d<mt::uint>& size)
			{
				WindowSize = size;
				if (size.Width != 0)
					InvWindowSize.Width = 1.0f / size.Width;
				else
					InvWindowSize.Width = 0.f;

				if (size.Height != 0)
					InvWindowSize.Height = 1.0f / size.Height;
				else
					InvWindowSize.Height = 0.f;
			}

			/** Used to notify the cursor that the window resizable settings changed. */
			void updateBorderSize(bool fullscreen, bool resizable)
			{
				if (!fullscreen)
				{
					if (resizable)
					{
						BorderX = GetSystemMetrics(SM_CXSIZEFRAME);
						BorderY = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYSIZEFRAME);
					}
					else
					{
						BorderX = GetSystemMetrics(SM_CXDLGFRAME);
						BorderY = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYDLGFRAME);
					}
				}
				else
				{
					BorderX = BorderY = 0;
				}
			}


			//! Sets the active cursor icon
			virtual void setActiveIcon(ECURSOR_ICON iconId);

			//! Gets the currently active icon
			virtual ECURSOR_ICON getActiveIcon() const
			{
				return ActiveIcon;
			}

			////! Add a custom sprite as cursor icon.
			//virtual ECURSOR_ICON addIcon(const SCursorSprite& icon);

			////! replace the given cursor icon.
			//virtual void changeIcon(ECURSOR_ICON iconId, const SCursorSprite& icon);

			//! Return a system-specific size which is supported for cursors. Larger icons will fail, smaller icons might work.
			virtual mt::dimension2di getSupportedIconSize() const;

			void update();

		private:

			//! Updates the internal cursor position
			void updateInternalCursorPosition();

			WinDevice* Device;
			mt::position2d<int> CursorPos;
			mt::dimension2d<mt::uint> WindowSize;
			mt::dimension2d<float> InvWindowSize;
			HWND HWnd;

			int BorderX, BorderY;
			mt::rect<int> ReferenceRect;
			bool UseReferenceRect;
			bool IsVisible;


			struct CursorFrameW32
			{
				CursorFrameW32() : IconHW(0) {}
				CursorFrameW32(HCURSOR icon) : IconHW(icon) {}

				HCURSOR IconHW;	// hardware cursor
			};

			struct CursorW32
			{
				CursorW32() {}
				explicit CursorW32(HCURSOR iconHw, mt::uint frameTime = 0) 
				{
					FrameTime = frameTime;
					Frames.push_back(CursorFrameW32(iconHw));
				}
				std::vector<CursorFrameW32> Frames;
				mt::uint FrameTime;
			};

			std::vector<CursorW32> Cursors;
			ECURSOR_ICON ActiveIcon;
			mt::uint ActiveIconStartTime;

			void initCursors();
		};
	}
}
#endif